/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "CameraMan.hpp"

#include <OGRE/Ogre.h>
#include <OIS/OIS.h>

namespace spelprog2 {

CameraMan::CameraMan(Ogre::Camera & cam, const Landscape & landscape, int width, int height) :
        mCamera(&cam),
        mLandscape(landscape),
        mTarget(0),
        mOrbiting(false),
        mZooming(false),
        mTopSpeed(100),
        mVelocity(Ogre::Vector3::ZERO),
		mZoomVelocity(Ogre::Vector3::ZERO),
        mGoingForward(false),
        mGoingBack(false),
        mGoingLeft(false),
        mGoingRight(false),
        mGoingUp(false),
        mGoingDown(false),
        mFastMove(false),
        mBorderMove(false),
        mHeight(height),
        mWidth(width),
		mReferencePoint(Ogre::Vector3(0.0f, 120.0f, 0.0f)),
		mRelativeCamera(Ogre::Vector3(-120.0f, 120.0f, -120.0f)),
		mOldRelativeCamera(Ogre::Vector3(-120.0f, 120.0f, -120.0f))
{
	setStyle(CS_FREELOOK);
	updateCamera();
}

CameraMan::~CameraMan()
{
}

void CameraMan::setTarget(Ogre::SceneNode* target)
{
	if (target != mTarget)
	{
		mTarget = target;
		if(target)
		{
			setYawPitchDist(Ogre::Degree(0), Ogre::Degree(15), 150);
			mCamera->setAutoTracking(true, mTarget);
		}
		else
		{
			mCamera->setAutoTracking(false);
		}

	}
}

void CameraMan::setYawPitchDist(Ogre::Radian yaw, Ogre::Radian pitch, Ogre::Real dist)
{
	mCamera->setPosition(mTarget->_getDerivedPosition());
	mCamera->setOrientation(mTarget->_getDerivedOrientation());
	mCamera->yaw(yaw);
	mCamera->pitch(-pitch);
	mCamera->moveRelative(Ogre::Vector3(0, 0, dist));
}

void CameraMan::setStyle(CameraStyle style)
{
	if (mStyle != CS_ORBIT && style == CS_ORBIT)
	{
		setTarget(mTarget ? mTarget : mCamera->getSceneManager()->getRootSceneNode());
		mCamera->setFixedYawAxis(true);
		manualStop();
		setYawPitchDist(Ogre::Degree(0), Ogre::Degree(15), 150);

	}
	else if (mStyle != CS_FREELOOK && style == CS_FREELOOK)
	{
		mCamera->setAutoTracking(false);
		mCamera->setFixedYawAxis(true);
	}
	else if (mStyle != CS_MANUAL && style == CS_MANUAL)
	{
		mCamera->setAutoTracking(false);
		manualStop();
	}
	mStyle = style;
}

void CameraMan::manualStop()
{
	mGoingForward = false;
	mGoingBack = false;
	mGoingLeft = false;
	mGoingRight = false;
	mGoingUp = false;
	mGoingDown = false;
	mVelocity = Ogre::Vector3::ZERO;
}

bool CameraMan::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	if (mStyle == CS_FREELOOK)
	{
		// build our acceleration vector based on keyboard input composite
		Ogre::Vector3 accel = Ogre::Vector3::ZERO;
		Ogre::Vector3 zoomAccel = Ogre::Vector3::ZERO;
		if (mGoingForward) accel += Ogre::Vector3(1,0,1) * mCamera->getDirection();
		if (mGoingBack) accel -= Ogre::Vector3(1,0,1) * mCamera->getDirection();
		if (mGoingRight) accel += Ogre::Vector3(1,0,1) * mCamera->getRight();
		if (mGoingLeft) accel -= Ogre::Vector3(1,0,1) * mCamera->getRight();
		if (mGoingUp) zoomAccel += mCamera->getPosition() - mReferencePoint ;
		if (mGoingDown) zoomAccel -= mCamera->getPosition() - mReferencePoint;

		
		if(mBorderMove)
		{
		    accel -= mMotionVector.y * Ogre::Vector3(1,0,1) * mCamera->getDirection();
		    accel += mMotionVector.x * Ogre::Vector3(1,0,1) * mCamera->getRight();
		}

		// if accelerating, try to reach top speed in a certain time
		Ogre::Real topSpeed = mFastMove ? mTopSpeed * 5 : mTopSpeed;
		if (accel.squaredLength() != 0)
		{
			accel.normalise();
			mVelocity += accel * topSpeed * evt.timeSinceLastFrame * 10;
		}
		// if not accelerating, try to stop in a certain time
		else mVelocity -= mVelocity * evt.timeSinceLastFrame * 10;

		Ogre::Real zoomTopSpeed = mFastMove ? mTopSpeed * 5 : mTopSpeed;
		if (zoomAccel.squaredLength() != 0)
		{
			zoomAccel.normalise();
			mZoomVelocity += zoomAccel * topSpeed * evt.timeSinceLastFrame * 10;
		}
		else mZoomVelocity -= mZoomVelocity * evt.timeSinceLastFrame * 10;

		Ogre::Real tooSmall = std::numeric_limits<Ogre::Real>::epsilon();

		// keep camera velocity below top speed and above epsilon
		if (mVelocity.squaredLength() > topSpeed * topSpeed)
		{
			mVelocity.normalise();
			mVelocity *= topSpeed;
		}
		else if (mVelocity.squaredLength() < tooSmall * tooSmall)
			mVelocity = Ogre::Vector3::ZERO;

		if (mZoomVelocity.squaredLength() > topSpeed * topSpeed)
		{
			mZoomVelocity.normalise();
			mZoomVelocity *= topSpeed;
		}
		else if (mZoomVelocity.squaredLength() < tooSmall * tooSmall)
			mZoomVelocity = Ogre::Vector3::ZERO;
		
		
		if(!(mGoingForward && mGoingBack && mGoingRight && mGoingLeft)){
			accel = Ogre::Vector3::ZERO;
		}
		if (mVelocity != Ogre::Vector3::ZERO) {
			mReferencePoint+=mVelocity*evt.timeSinceLastFrame;
			updateCamera();
		}
		if (mZoomVelocity != Ogre::Vector3::ZERO) {
			mRelativeCamera+=mZoomVelocity*evt.timeSinceLastFrame;	
			updateCamera();
		}
	}

	return true;
}

void CameraMan::injectKeyDown(const OIS::KeyEvent& evt)
{
	if (mStyle == CS_FREELOOK)
	{
		if (evt.key == OIS::KC_W || evt.key == OIS::KC_UP) mGoingForward = true;
		else if (evt.key == OIS::KC_S || evt.key == OIS::KC_DOWN) mGoingBack = true;
		else if (evt.key == OIS::KC_A || evt.key == OIS::KC_LEFT) mGoingLeft = true;
		else if (evt.key == OIS::KC_D || evt.key == OIS::KC_RIGHT) mGoingRight = true;
		else if (evt.key == OIS::KC_PGUP) mGoingUp = true;
		else if (evt.key == OIS::KC_PGDOWN) mGoingDown = true;
		else if (evt.key == OIS::KC_LSHIFT) mFastMove = true;
	}
}

void CameraMan::injectKeyUp(const OIS::KeyEvent& evt)
{
	if (mStyle == CS_FREELOOK)
	{
		if (evt.key == OIS::KC_W || evt.key == OIS::KC_UP) mGoingForward = false;
		else if (evt.key == OIS::KC_S || evt.key == OIS::KC_DOWN) mGoingBack = false;
		else if (evt.key == OIS::KC_A || evt.key == OIS::KC_LEFT) mGoingLeft = false;
		else if (evt.key == OIS::KC_D || evt.key == OIS::KC_RIGHT) mGoingRight = false;
		else if (evt.key == OIS::KC_PGUP) mGoingUp = false;
		else if (evt.key == OIS::KC_PGDOWN) mGoingDown = false;
		else if (evt.key == OIS::KC_LSHIFT) mFastMove = false;
	}
}

void CameraMan::injectMouseMove(const OIS::MouseEvent& evt, bool idle)
{
    if(idle)
    {
        float border_sector = 0.005f;
        float w = float(mWidth),
              h = float(mHeight);
        
        if(evt.state.X.abs < w*border_sector ||
           evt.state.X.abs > (1.0-border_sector)*w ||
           evt.state.Y.abs < h*border_sector ||
           evt.state.Y.abs > (1.0-border_sector)*h)
        {
            mMotionVector = Ogre::Vector3(evt.state.X.abs - Ogre::Real(0.5) * w, evt.state.Y.abs - Ogre::Real(0.5) * h, 0);
            mMotionVector.normalise();
            mBorderMove = true;
        }
        else
        {
            mBorderMove = false;
        }
    }
	else if (mStyle == CS_ORBIT)
	{
		Ogre::Real dist = (mCamera->getPosition() - mTarget->_getDerivedPosition()).length();

		if (mOrbiting)   // yaw around the target, and pitch locally
		{
			mCamera->setPosition(mTarget->_getDerivedPosition());

			mCamera->yaw(Ogre::Degree(-evt.state.X.rel * 0.25f));
			mCamera->pitch(Ogre::Degree(-evt.state.Y.rel * 0.25f));

			mCamera->moveRelative(Ogre::Vector3(0, 0, dist));

			// don't let the camera go over the top or around the bottom of the target
		}
		else if (mZooming)  // move the camera toward or away from the target
		{
			// the further the camera is, the faster it moves
			mCamera->moveRelative(Ogre::Vector3(0, 0, evt.state.Y.rel * 0.004f * dist));
		}
		else if (evt.state.Z.rel != 0)  // move the camera toward or away from the target
		{
			// the further the camera is, the faster it moves
			mCamera->moveRelative(Ogre::Vector3(0, 0, -evt.state.Z.rel * 0.0008f * dist));
		}

	}
	else if (mStyle == CS_FREELOOK)
	{
		/*if (evt.state.Z.rel != 0){
		  mCamera->move(Ogre::Vector3(0, -evt.state.Z.rel * 0.08f, 0));
		}*/

		//Ogre::Real topSpeed = mFastMove ? mTopSpeed * 5 : mTopSpeed;
		mRelativeCamera+= (mCamera->getRight() * Ogre::Real(evt.state.X.rel) * 0.0025f * mRelativeCamera.length());
		mRelativeCamera+= (mCamera->getUp() * Ogre::Real(evt.state.Y.rel) * 0.0025f * mRelativeCamera.length());


		//mCamera->yaw(Ogre::Degree(-evt.state.X.rel * 0.15f));
		
		//mCamera->rotate(mCamera->getPosition()+ (mCamera->getDirection()*Ogre::Vector3(100.0f,0,100.0f)), Ogre::Radian(0.00004f * -evt.state.X.rel));
		//mCamera->pitch(Ogre::Degree(-evt.state.Y.rel * 0.15f));

		updateCamera();
	}
}

void CameraMan::injectMouseDown(const OIS::MouseEvent& evt, OIS::MouseButtonID id)
{
	if (mStyle == CS_ORBIT)
	{
		if (id == OIS::MB_Left) mOrbiting = true;
		else if (id == OIS::MB_Right) mZooming = true;
	}
}

void CameraMan::injectMouseUp(const OIS::MouseEvent& evt, OIS::MouseButtonID id)
{
	if (mStyle == CS_ORBIT)
	{
		if (id == OIS::MB_Left) mOrbiting = false;
		else if (id == OIS::MB_Right) mZooming = false;
	}
}

void CameraMan::updateCamera(){
	if(mRelativeCamera.length() <10){
		mRelativeCamera=mOldRelativeCamera;
	}
	if(mRelativeCamera.length() > 600){
		mRelativeCamera=mOldRelativeCamera;
	}
	if((Ogre::Vector3(0,1,0)*mRelativeCamera).length() < 10){
		mRelativeCamera=mOldRelativeCamera;
	}
	if(mRelativeCamera.angleBetween(Ogre::Vector3(0,1,0)) < Ogre::Radian(0.1f)){
		mRelativeCamera=mOldRelativeCamera;

	}
		mCamera->setPosition(mReferencePoint+mRelativeCamera);
		mCamera->lookAt(mReferencePoint);

		mOldRelativeCamera=mRelativeCamera;

	
	//mCamera->lookAt(Ogre::Vector3(0.0f, -1000.0f,0.0f)+ + mCamera->getDirection() * Ogre::Vector3(1000.0f,0.0f,1000.0f));
}

}; //namespace spelprog2
